﻿/*********************************************************************************
 * Copyright (c) 2013, Christian Etter info@christian-etter.de
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

/// <summary>Extension method for initializing a RSACryptoServiceProvider from PEM data string.</summary>
public static class RSACryptoServiceProviderExtension
{
	#region Methods

	/// <summary>Extension method which initializes an RSACryptoServiceProvider from a DER public key blob.</summary>
	public static void LoadPublicKeyDER( this RSACryptoServiceProvider provider, byte[] DERData )
	{
		byte[] RSAData = RSACryptoServiceProviderExtension.GetRSAFromDER( DERData );
		byte[] publicKeyBlob = RSACryptoServiceProviderExtension.GetPublicKeyBlobFromRSA( RSAData );
		provider.ImportCspBlob( publicKeyBlob );
	}

	/// <summary>Extension method which initializes an RSACryptoServiceProvider from a DER private key blob.</summary>
	public static void LoadPrivateKeyDER( this RSACryptoServiceProvider provider, byte[] DERData )
	{
		byte[] privateKeyBlob = RSACryptoServiceProviderExtension.GetPrivateKeyDER( DERData );
		provider.ImportCspBlob( privateKeyBlob );
	}

	/// <summary>Extension method which initializes an RSACryptoServiceProvider from a PEM public key string.</summary>
	public static void LoadPublicKeyPEM( this RSACryptoServiceProvider provider, string sPEM )
	{
		byte[] DERData = RSACryptoServiceProviderExtension.GetDERFromPEM( sPEM );
		RSACryptoServiceProviderExtension.LoadPublicKeyDER( provider, DERData );
	}

	/// <summary>Extension method which initializes an RSACryptoServiceProvider from a PEM private key string.</summary>
	public static void LoadPrivateKeyPEM( this RSACryptoServiceProvider provider, string sPEM )
	{
		byte[] DERData = RSACryptoServiceProviderExtension.GetDERFromPEM( sPEM );
		RSACryptoServiceProviderExtension.LoadPrivateKeyDER( provider, DERData );
	}

	/// <summary>Returns a public key blob from an RSA public key.</summary>
	internal static byte[] GetPublicKeyBlobFromRSA( byte[] RSAData )
	{
		byte[] data = null;
		UInt32 dwCertPublicKeyBlobSize = 0;
		if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING,
			new IntPtr( (int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB ), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE,
			data, ref dwCertPublicKeyBlobSize ) )
		{
			data = new byte[ dwCertPublicKeyBlobSize ];
			if ( !RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING,
				new IntPtr( (int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB ), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE,
				data, ref dwCertPublicKeyBlobSize ) )
				throw new Win32Exception( Marshal.GetLastWin32Error() );
		}
		else
			throw new Win32Exception( Marshal.GetLastWin32Error() );
		return data;
	}

	/// <summary>Converts DER binary format to a CAPI CRYPT_PRIVATE_KEY_INFO structure.</summary>
	internal static byte[] GetPrivateKeyDER( byte[] DERData )
	{
		byte[] data = null;
		UInt32 dwRSAPrivateKeyBlobSize = 0;
		IntPtr pRSAPrivateKeyBlob = IntPtr.Zero;
		if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.PKCS_RSA_PRIVATE_KEY ),
			DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwRSAPrivateKeyBlobSize ) )
		{
			data = new byte[ dwRSAPrivateKeyBlobSize ];
			if ( !RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.PKCS_RSA_PRIVATE_KEY ),
				DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwRSAPrivateKeyBlobSize ) )
				throw new Win32Exception( Marshal.GetLastWin32Error() );
		}
		else
			throw new Win32Exception( Marshal.GetLastWin32Error() );
		return data;
	}

	/// <summary>Converts DER binary format to a CAPI CERT_PUBLIC_KEY_INFO structure containing an RSA key.</summary>
	internal static byte[] GetRSAFromDER( byte[] DERData )
	{
		byte[] data = null;
		byte[] publicKey = null;
		CERT_PUBLIC_KEY_INFO info;
		UInt32 dwCertPublicKeyInfoSize = 0;
		IntPtr pCertPublicKeyInfo = IntPtr.Zero;
		if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO ),
			DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize ) )
		{
			data = new byte[ dwCertPublicKeyInfoSize ];
			if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO ),
				DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize ) )
			{
				GCHandle handle = GCHandle.Alloc( data, GCHandleType.Pinned );
				try
				{
					info = (CERT_PUBLIC_KEY_INFO)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof( CERT_PUBLIC_KEY_INFO ) );
					publicKey = new byte[ info.PublicKey.cbData ];
					Marshal.Copy( info.PublicKey.pbData, publicKey, 0, publicKey.Length );
				}
				finally
				{
					handle.Free();
				}
			}
			else
				throw new Win32Exception( Marshal.GetLastWin32Error() );
		}
		else
			throw new Win32Exception( Marshal.GetLastWin32Error() );
		return publicKey;
	}

	/// <summary>Extracts the binary data from a PEM file.</summary>
	internal static byte[] GetDERFromPEM( string sPEM )
	{
		UInt32 dwSkip, dwFlags;
		UInt32 dwBinarySize = 0;

		if ( !RSACryptoServiceProviderExtension.CryptStringToBinary( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, null, ref dwBinarySize, out dwSkip, out dwFlags ) )
			throw new Win32Exception( Marshal.GetLastWin32Error() );

		byte[] decodedData = new byte[ dwBinarySize ];
		if ( !RSACryptoServiceProviderExtension.CryptStringToBinary( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, decodedData, ref dwBinarySize, out dwSkip, out dwFlags ) )
			throw new Win32Exception( Marshal.GetLastWin32Error() );
		return decodedData;
	}

	#endregion Methods

	#region P/Invoke Constants

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_ACQUIRE_CONTEXT_FLAGS : uint
	{
		CRYPT_NEWKEYSET = 0x8,
		CRYPT_DELETEKEYSET = 0x10,
		CRYPT_MACHINE_KEYSET = 0x20,
		CRYPT_SILENT = 0x40,
		CRYPT_DEFAULT_CONTAINER_OPTIONAL = 0x80,
		CRYPT_VERIFYCONTEXT = 0xF0000000
	}

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_PROVIDER_TYPE : uint
	{
		PROV_RSA_FULL = 1
	}

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_DECODE_FLAGS : uint
	{
		NONE = 0,
		CRYPT_DECODE_ALLOC_FLAG = 0x8000
	}

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_ENCODING_FLAGS : uint
	{
		PKCS_7_ASN_ENCODING = 0x00010000,
		X509_ASN_ENCODING = 0x00000001,
	}

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_OUTPUT_TYPES : int
	{
		X509_PUBLIC_KEY_INFO = 8,
		RSA_CSP_PUBLICKEYBLOB = 19,
		PKCS_RSA_PRIVATE_KEY = 43,
		PKCS_PRIVATE_KEY_INFO = 44
	}

	/// <summary>Enumeration derived from Crypto API.</summary>
	internal enum CRYPT_STRING_FLAGS : uint
	{
		CRYPT_STRING_BASE64HEADER = 0,
		CRYPT_STRING_BASE64 = 1,
		CRYPT_STRING_BINARY = 2,
		CRYPT_STRING_BASE64REQUESTHEADER = 3,
		CRYPT_STRING_HEX = 4,
		CRYPT_STRING_HEXASCII = 5,
		CRYPT_STRING_BASE64_ANY = 6,
		CRYPT_STRING_ANY = 7,
		CRYPT_STRING_HEX_ANY = 8,
		CRYPT_STRING_BASE64X509CRLHEADER = 9,
		CRYPT_STRING_HEXADDR = 10,
		CRYPT_STRING_HEXASCIIADDR = 11,
		CRYPT_STRING_HEXRAW = 12,
		CRYPT_STRING_NOCRLF = 0x40000000,
		CRYPT_STRING_NOCR = 0x80000000
	}

	#endregion P/Invoke Constants

	#region P/Invoke Structures

	/// <summary>Structure from Crypto API.</summary>
	[StructLayout( LayoutKind.Sequential )]
	internal struct CRYPT_OBJID_BLOB
	{
		internal UInt32 cbData;
		internal IntPtr pbData;
	}

	/// <summary>Structure from Crypto API.</summary>
	[StructLayout( LayoutKind.Sequential )]
	internal struct CRYPT_ALGORITHM_IDENTIFIER
	{
		internal IntPtr pszObjId;
		internal CRYPT_OBJID_BLOB Parameters;
	}

	/// <summary>Structure from Crypto API.</summary>
	[StructLayout( LayoutKind.Sequential )]
	struct CRYPT_BIT_BLOB
	{
		internal UInt32 cbData;
		internal IntPtr pbData;
		internal UInt32 cUnusedBits;
	}

	/// <summary>Structure from Crypto API.</summary>
	[StructLayout( LayoutKind.Sequential )]
	struct CERT_PUBLIC_KEY_INFO
	{
		internal CRYPT_ALGORITHM_IDENTIFIER Algorithm;
		internal CRYPT_BIT_BLOB PublicKey;
	}

	#endregion P/Invoke Structures

	#region P/Invoke Functions

	/// <summary>Function for Crypto API.</summary>
	[DllImport( @"advapi32.dll", SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptDestroyKey( IntPtr hKey );

	/// <summary>Function for Crypto API.</summary>
	[DllImport( @"advapi32.dll", SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptImportKey( IntPtr hProv, byte[] pbKeyData, UInt32 dwDataLen, IntPtr hPubKey, UInt32 dwFlags, ref IntPtr hKey );

	/// <summary>Function for Crypto API.</summary>
	[DllImport( "advapi32.dll", SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptReleaseContext( IntPtr hProv, Int32 dwFlags );

	/// <summary>Function for Crypto API.</summary>
	[DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptAcquireContext( ref IntPtr hProv, string pszContainer, string pszProvider, CRYPT_PROVIDER_TYPE dwProvType, CRYPT_ACQUIRE_CONTEXT_FLAGS dwFlags );

	/// <summary>Function from Crypto API.</summary>
	[DllImport( "crypt32.dll", SetLastError = true, CharSet = CharSet.Auto )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptStringToBinary( string sPEM, UInt32 sPEMLength, CRYPT_STRING_FLAGS dwFlags, [Out] byte[] pbBinary, ref UInt32 pcbBinary, out UInt32 pdwSkip, out UInt32 pdwFlags );

	/// <summary>Function from Crypto API.</summary>
	[DllImport( "crypt32.dll", SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptDecodeObjectEx( CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS dwFlags, IntPtr pDecodePara, ref byte[] pvStructInfo, ref UInt32 pcbStructInfo );

	/// <summary>Function from Crypto API.</summary>
	[DllImport( "crypt32.dll", SetLastError = true )]
	[return: MarshalAs( UnmanagedType.Bool )]
	internal static extern bool CryptDecodeObject( CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS flags, [In, Out] byte[] pvStructInfo, ref UInt32 cbStructInfo );

	#endregion P/Invoke Functions
}
